﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Media3D;
using System.Xml;
using System.Globalization;
using System.Windows.Media;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace SkeletalAnimation3D.Core
{
    public class Bone : INotifyPropertyChanged
    {
        private int _id;

        public int Id
        {
            get { return _id; }
            set { if (_id != value) { _id = value; OnPropertyChanged("Id"); } }
        }

        private string _name;

        public string Name
        {
            get { return _name; }
            set { if (String.Compare(_name, value) != 0) { _name = value; OnPropertyChanged("Name"); } }
        }

        private Point3D _position;

        public Point3D Position
        {
            get { return _position; }
            set { if (_position != value) { _position = value; OnPropertyChanged("Position"); } }
        }

        private Quaternion _orientation;

        public Quaternion Orientation
        {
            get
            {
                return _orientation;
            }
            set
            {
                if (_orientation != value)
                {
                    _orientation = value;
                    OnPropertyChanged("Orientation");
                }
            }
        }

        private double _size;

        public double Size
        {
            get { return _size; }
            set { if (_size != value) { _size = value; OnPropertyChanged("Size"); } }
        }

        private Bone _parent;

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Bone Parent
        {
            get { return _parent; }
            set { if (_parent != value) { _parent = value; OnPropertyChanged("Parent"); } }
        }



        private Int32Collection _meshPointIndices = new Int32Collection();

        public Int32Collection MeshPointIndices
        {
            get { return _meshPointIndices; }
            set { if (_meshPointIndices != value) { _meshPointIndices = value; OnPropertyChanged("MeshPointIndices"); } }
        }

        private DoubleCollection _meshPointWeights = new DoubleCollection();

        public DoubleCollection MeshPointWeights
        {
            get { return _meshPointWeights; }
            set { if (_meshPointWeights != value) { _meshPointWeights = value; OnPropertyChanged("MeshPointWeights"); } }
        }
        private BoneCollection _childBones = new BoneCollection();

        public BoneCollection ChildBones
        {
            get { return _childBones; }
            set { if (_childBones != value) { _childBones = value; OnPropertyChanged("ChildBones"); } }
        }

        internal BoneInfo GetBoneInfo()
        {
            return GetBoneInfo(null);
        }

        private BoneInfo GetBoneInfo(BoneInfo parent)
        {
            BoneInfo boneInfo = new BoneInfo { Id = _id, Parent = parent, Name = _name, Position = _position, Orientation = _orientation, Size = _size };
            boneInfo.Children = new ObservableCollection<BoneInfo>();
            foreach (Bone child in _childBones)
            {
                boneInfo.Children.Add(child.GetBoneInfo(boneInfo));
            }
            return boneInfo;
        }

        internal void CopyBoneData(SkeletonInfo target)
        {
            target.SetBoneData(this._id, this.Name, this._position, this._size, this._orientation);
            if (_childBones != null && _childBones.Count > 0)
            {
                for (int i = 0; i < _childBones.Count; i++)
                {
                    _childBones[i].CopyBoneData(target);
                }
            }

        }

        internal Int32Collection GetAttachedPoints(int boneId)
        {
            if (_id == boneId)
            {
                return _meshPointIndices;
            }
            else
            {
                if (_childBones != null)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        Int32Collection points = _childBones[i].GetAttachedPoints(boneId);
                        if (points != null)
                        {
                            return points;
                        }
                    }
                    return null;
                }
                return null;
            }
        }

        internal DoubleCollection GetPointWeights(int boneId)
        {
            if (_id == boneId)
            {
                return _meshPointWeights;
            }
            else
            {
                if (_childBones != null)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        DoubleCollection pointWeights = _childBones[i].GetPointWeights(boneId);
                        if (pointWeights != null)
                        {
                            return pointWeights;
                        }
                    }
                    return null;
                }
                return null;
            }
        }

        internal bool SetBoneSize(int boneId, double size)
        {
            if (Id == boneId)
            {
                Size = size;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].SetBoneSize(boneId, size))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }


        internal bool SetBoneName(int boneId, string name)
        {
            if (Id == boneId)
            {
                Name = name;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].SetBoneName(boneId, name))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        internal bool MoveBone(int boneId, Vector3D offset)
        {
            if (Id == boneId)
            {
                Position += offset;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].MoveBone(boneId, offset))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        internal bool AddBone(int parentBoneId, int id, string name, Point3D position, double size)
        {
            if (Id == parentBoneId)
            {
                Bone newBone = new Bone();
                newBone.Id = id;
                newBone.Name = name;
                newBone.Position = position;
                newBone.Size = size;
                newBone.Orientation = Quaternion.Identity;
                newBone.Parent = this;
                this.ChildBones.Add(newBone);
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].AddBone(parentBoneId, id, name, position, size))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        internal int GetMaxBoneId()
        {
            int maxId = this.Id;
            if (ChildBones != null && ChildBones.Count > 0)
            {
                for (int i = 0; i < ChildBones.Count; i++)
                {
                    maxId = Math.Max(maxId, ChildBones[i].GetMaxBoneId());
                }
            }
            return maxId;
        }

        internal bool AttachPointsToBone(int boneId, Int32Collection pointIds)
        {
            if (Id == boneId)
            {
                Int32Collection newAttachedPoints = new Int32Collection(this.MeshPointIndices);
                DoubleCollection newPointWeights = new DoubleCollection(this.MeshPointWeights);
                for (int i = 0; i < pointIds.Count; i++)
                {
                    if (!newAttachedPoints.Contains(pointIds[i]))
                    {
                        newAttachedPoints.Add(pointIds[i]);
                        newPointWeights.Add(1);
                    }
                }
                this.MeshPointIndices = newAttachedPoints;
                this.MeshPointWeights = newPointWeights;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].AttachPointsToBone(boneId, pointIds))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        internal bool DetachPointsFromBone(int boneId, Int32Collection pointIds)
        {
            if (Id == boneId)
            {
                Int32Collection newAttachedPoints = new Int32Collection(this.MeshPointIndices);
                DoubleCollection newPointWeights = new DoubleCollection(this.MeshPointWeights);
                for (int i = 0; i < pointIds.Count; i++)
                {
                    int index = newAttachedPoints.IndexOf(pointIds[i]);
                    if (index != -1)
                    {
                        newAttachedPoints.RemoveAt(index);
                        newPointWeights.RemoveAt(index);
                    }
                }
                this.MeshPointIndices = newAttachedPoints;
                this.MeshPointWeights = newPointWeights;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].DetachPointsFromBone(boneId, pointIds))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        internal bool SetPointWeightsOnBone(int boneId, Int32Collection pointIds, double weight)
        {
            if (Id == boneId)
            {
                DoubleCollection newPointWeights = new DoubleCollection(this.MeshPointWeights);
                for (int i = 0; i < pointIds.Count; i++)
                {
                    int index = MeshPointIndices.IndexOf(pointIds[i]);
                    if (index != -1)
                    {
                        newPointWeights[index] = weight;
                    }
                }
                this.MeshPointWeights = newPointWeights;
                return true;
            }
            else
            {
                if (_childBones != null && _childBones.Count > 0)
                {
                    for (int i = 0; i < _childBones.Count; i++)
                    {
                        if (_childBones[i].SetPointWeightsOnBone(boneId, pointIds, weight))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        public Bone GetBone(int boneId)
        {
            if (Id == boneId)
            {
                return this;
            }
            else
            {
                if (ChildBones != null && ChildBones.Count > 0)
                {
                    for (int i = 0; i < ChildBones.Count; i++)
                    {
                        Bone bone = ChildBones[i].GetBone(boneId);
                        if (bone != null)
                        {
                            return bone;
                        }
                    }
                }
            }
            return null;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler p = PropertyChanged;
            if (p != null)
            {
                p(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
